/*
	- Qua 13 Fev 2008 12:18:32 BRT
	- Sistema de Gerenciamento de Memória adaptado do [Projeto SOmBRA]
	
*/ 

#include	<system/kernel.h>
#include	<types.h>
#include	<system/libk.h>
#include	<elf.h>

static uint_t		g_curr_heap_size, g_max_heap_size;
static char_t		*g_heap;
static mmblock_t	*mmpi = NULL;

void	kbasic_mm_setup()
{
	module_t	*mod;
	int_t		startheap = 0;
	int_t		i;

	kprintf("Memory Manager:\n");

	if(!CHECK_FLAG(kbasic_get_flags(), ERR_PAGING))
		kbasic_skip_main(ERR_PAGING);

	// Se tiver módulos procura o endereço final dos módulos.
	if(mbi->flags & 0x8)
	{
		kprintf("\tModules space reserved.\n");
		for(i = 0; i < mbi->mods_count; i++)
		{
			mod = (module_t *)(mbi->mods_addr - virt2phys) + i;
			if(mod->mod_end > startheap)
			{
				kprintf("\t\tModule [");
				if(mod->string)
					kprintf((const char_t*)(mod->string - virt2phys));
				kprintf("] [%d KB]\n", (mod->mod_end - mod->mod_start) / 1024);
				startheap = mod->mod_end;
			}
		}
	}

	startheap -= virt2phys;	// converte endereço final dos módulos para endereço físico

	// Talvez o kernel foi lido depois, então o endereço final será o g_end.
	if((uint_t)g_end > startheap)
	{
		startheap = (uint_t)g_end;
	}

	// Setaremos agora o heap do kernel.
	g_heap = (char_t*)startheap;

	// Cálculo do tamanho do heap.
	g_max_heap_size = (((1024 + mbi->mem_upper) * 1024 - virt2phys) - startheap); // TODO: excluir diretorio e tabelas de pagina no fim da memoria

	kprintf("\tKernel Heap = %d KB @ 0x%x\n", g_max_heap_size / 1024, startheap);

	mmpi = (mmblock_t*)(g_heap);
    	mmpi->magic = NM_MEMORY;
   	mmpi->size  = 1;
   	mmpi->next  = NULL;	        
        
    	g_curr_heap_size = 0;
}

void*	kmalloc(ulong_t size)
{
	mmblock_t	*pn,*pt;
	int_t	total_size, n;
	
	// Calcula o espaço de memória que será necessário para armazenar o bloco e os dados.
	total_size = sizeof(mmblock_t)+size;
	
	// Se o tamanho for zero retorna NULL.
	if (size==0){
		return NULL;
	}
	
	// Percorrer a lista até encontrar um espaço capaz de armazenar o bloco e os dados.
	pn = mmpi;
	while (pn->next!=NULL)
	{
		n = ((int)pn->next)-((int)pn+sizeof(mmblock_t)+pn->size);
		// Em caso de uma lacuna na memória.
		if (total_size<=n)
		{
			pt = (mmblock_t*)((int)pn+(pn->size)+sizeof(mmblock_t));
			pt->magic = NM_MEMORY;
			pt->size  = size;
			pt->next  = pn->next;
			pn->next  = pt;			
			
			g_curr_heap_size += total_size;
			
			return (void*)((int)pt+sizeof(mmblock_t));
		}
		if (pn->magic != NM_MEMORY)
		{
			kprintf("Kernel Heap is corrupt!\n");
			kbasic_skip_main(-1);
			return NULL;
		}
		pn = pn->next;
	}
	
	// Armazena no fim da memória.
	pt = (mmblock_t*)((int)pn+(pn->size)+sizeof(mmblock_t));
	pt->magic = NM_MEMORY;
	pt->size  = size;
	pt->next  = NULL;
	pn->next  = pt;
	
	g_curr_heap_size += total_size;
	return (void*)((int)pt+sizeof(mmblock_t));	
}

void*	kzmalloc(ulong_t size)
{
	void*	ptr = kmalloc(size);
	if(ptr)
		bzero(ptr, size);

	return	ptr;
}

void	kfree(void *mm)
{
	mmblock_t *pt, *pn;
	
	// Pega o bloco referente aquele endereço de memória.
	pt = (mmblock_t*)((int)mm-sizeof(mmblock_t));
	
	// Verifica se realmente é um bloco de memória.
	if (pt->magic!=NM_MEMORY)
	{
		kprintf("kfree error: 0x%x is not a valid memory block.", mm);
		return;
	}
	
	g_curr_heap_size -= (pt->size + sizeof(mmblock_t));

	// Mantém a lista encadeada e consistente.
	pn = mmpi;
	while(pn->next!=NULL)
	{
		if (pn->next==pt)
		{
			pn->next = pt->next;
			return;
		}
		pn = pn->next;
  	}  	
}

void* krealloc(void *mm, ulong_t size)
{
	void *mmnew;
	mmnew = kmalloc(size);	
	if ((mmnew!=NULL)&&(mm!=NULL))
	{	
		memcpy(mmnew, mm, size);		
		kfree(mm);
	}
	return mmnew;
}
